import time
import os.path
from framework.logger import Logger
import re
from selenium.common.exceptions import TimeoutException
import selenium.webdriver.support.expected_conditions as EC
import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import win32api
import win32con


# create a logger instance
logger = Logger(logger="BasePage")


class BasePage(object):
    """
    定义一个页面基类，让所有页面都继承这个类，封装一些常用的页面操作方法到这个类
    """
    def __init__(self, driver):
        self.driver = driver

    # 退出浏览器结束测试
    def quit_browser(self):
        self.driver.quit()

    # 浏览器前进操作
    def forward(self):
        self.driver.forward()
        logger.info("Click forward on current page.")

    # 刷新当前页面
    def refresh(self, log_msg=""):
        try:
            self.driver.refresh()  # 刷新方法 refresh
            logger.info("refresh window %s" % log_msg)
        except Exception as e:
            logger.error("fail to refresh window %s" % log_msg)
            self.get_windows_img(error_info="fail to refresh window %s" % log_msg)

    # 浏览器后退操作
    def back(self):
        self.driver.back()
        logger.info("Click back on current page.")

    # 获取当前URl
    def current_url(self):
        url = self.driver.current_url
        logger.info("get current url")
        return url

    # 隐式等待
    def wait(self, seconds):
        self.driver.implicitly_wait(seconds)
        logger.info("wait for %d seconds." % seconds)

    # 点击关闭当前窗口
    def close(self):
        try:
            self.driver.close()
            logger.info("Closing and quit the browser.")
        except NameError as e:
            logger.error("Failed to quit the browser with %s" % e)

    # 保存图片
    def get_windows_img(self, error_info="error_info"):
        """
        :param error_info: 错误信息或者是操作信息
        :return:
        """
        file_path = os.getcwd() + "/screenshots/"
        rq = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time()))  # 2019-01-09_15-12-47
        # 文件名中不能有\/:*?"<>|符号，所以替换为"_"
        error_info = error_info.replace("=>", "_").replace("\\", "_").replace("/", "_")\
            .replace(":", "_").replace("*", "_").replace("'", "_")
        error_info = error_info.replace("?", "_").replace("\"", "_")\
            .replace("<", "_").replace(">", "_").replace("|", "_")

        screen_name = rq + "_" + error_info + ".png"  # 截图命名 2019-01-09_15-14-12_XXX.png
        full_path = file_path + screen_name  # 截图命名 2019-01-09_15-14-12_XXX.png
        try:
            self.driver.get_screenshot_as_file(full_path)
            logger.info("截图--%s报错,图片名--%s" % (error_info, screen_name))
        except NameError as e:
            logger.error("%s截图失败，reason: %s" % (error_info, e))

    # 直接定位元素方法
    def find_element(self, selector, parent_element=None, isshow_error=0):
        """
         (ps:这个地方为什么是根据=>来切割字符串，请看页面里定位元素的方法
         login_lnk = "xpath => //*[@id='u1']/a[7]"  # 百度首页登录链接定位
         如果采用等号，结果很多xpath表达式中包含一个=，这样会造成切割不准确，影响元素定位)
        :param selector: 格式 "id=>kw"
        :param parent_element: 传入父元素，则找此父元素的子元素
        :param isshow_error: 控制未找到元素时是否报错，默认为0---报错，1---不报错
        :return: element
        """
        element = None
        driver_or_el = parent_element if parent_element else self.driver
        if "=>" not in selector:
            # 如无=>符号，默认使用id方式查找
            return driver_or_el.find_element_by_id(selector)
        selector_by = selector.split("=>")[0]
        selector_value = selector.split("=>")[1]
        try:
            if selector_by == "i" or selector_by == "id":
                element = driver_or_el.find_element_by_id(selector_value)
            elif selector_by == "n" or selector_by == "name":
                element = driver_or_el.find_element_by_name(selector_value)
            elif selector_by == "c" or selector_by == "class":
                element = driver_or_el.find_element_by_class_name(selector_value)
            elif selector_by == "l" or selector_by == "link_text":
                element = driver_or_el.find_element_by_link_text(selector_value)
            elif selector_by == "p" or selector_by == "partial_link_text":
                element = driver_or_el.find_element_by_partial_link_text(selector_value)
            elif selector_by == "t" or selector_by == "tag_name":
                element = driver_or_el.find_element_by_tag_name(selector_value)
            elif selector_by == "x" or selector_by == "xpath":
                element = driver_or_el.find_element_by_xpath(selector_value)
            elif selector_by == "css" or selector_by == "css_selector":
                element = driver_or_el.find_element_by_css_selector(selector_value)
            else:
                raise NameError("Please enter a valid type of target elements.")
        except Exception as e:
            if isshow_error == 0:
                logger.error("find element failed, reason: %s" % e)
                self.get_windows_img(error_info="find_element_%s_%s" % (selector_by, selector_value))  # take screenshot
        else:
            logger.info("Had find the element successful by %s value: %s "
                        % (selector_by, selector_value))
        return element

    # 定位多个相同元素方法
    def find_elements(self, selector, isshow_error=0):
        """
         这个地方为什么是根据=>来切割字符串，请看页面里定位元素的方法
         submit_btn = "id=>su"
         login_lnk = "xpath => //*[@id='u1']/a[7]"  # 百度首页登录链接定位
         如果采用等号，结果很多xpath表达式中包含一个=，这样会造成切割不准确，影响元素定位
        :param selector: 格式 "id=>kw"
        :param isshow_error: 控制未找到元素时是否报错，默认为0---报错，1---不报错
        :return: elements，返回一个element的list列表
        """
        elements = None
        if "=>" not in selector:
            # 如无=>符号，默认使用id方式查找
            return self.driver.find_elements_by_id(selector)
        selector_by = selector.split("=>")[0]
        selector_value = selector.split("=>")[1]
        try:
            if selector_by == "i" or selector_by == "id":
                elements = self.driver.find_elements_by_id(selector_value)
            elif selector_by == "n" or selector_by == "name":
                elements = self.driver.find_elements_by_name(selector_value)
            elif selector_by == "c" or selector_by == "class":
                elements = self.driver.find_elements_by_class_name(selector_value)
            elif selector_by == "l" or selector_by == "link_text":
                elements = self.driver.find_elements_by_link_text(selector_value)
            elif selector_by == "p" or selector_by == "partial_link_text":
                elements = self.driver.find_elements_by_partial_link_text(selector_value)
            elif selector_by == "t" or selector_by == "tag_name":
                elements = self.driver.find_elements_by_tag_name(selector_value)
            elif selector_by == "x" or selector_by == "xpath":
                elements = self.driver.find_elements_by_xpath(selector_value)
            elif selector_by == "css" or selector_by == "css_selector":
                elements = self.driver.find_elements_by_css_selector(selector_value)
            else:
                raise NameError("Please enter a valid type of targeting elements.")
        except Exception as e:
            if isshow_error == 0:
                logger.error("find elements failed, reason: %s" % e)
                self.get_windows_img(error_info="find_elements_%s_%s" % (selector_by, selector_value))  # 截图
        else:
            logger.info("Had find the elements successful by %s value: %s "
                        % (selector_by, selector_value))
        return elements

    # 输入文本
    def input_text(self, selector=None, element=None, text=""):

        if selector:
            el = self.find_element(selector)
        elif element:
            el = element
        else:
            raise Exception("Parameter incoming error!")
        el.clear()
        try:
            el.send_keys(text)
            logger.info("Had input text \' %s \' in inputBox" % text)
        except NameError as e:
            logger.error("Failed to input text in input box with %s" % e)
            self.get_windows_img(selector)

    # 清除文本框
    def clear(self, selector, element):
        """
        清除文本框
        :param selector: 传入格式 "id=>kw"
        :param element:  传入element
        :return: 无
        """
        if selector:
            el = self.find_element(selector)
        elif element:
            el = element
        else:
            el = ""
            logger.error("调用clear函数传入参数错误")
        try:
            el.clear()
            logger.info("Clear text in input box before input.")
        except NameError as e:
            logger.error("Failed to clear in input box with %s" % e)
            self.get_windows_img("clear fail")

    # 点击元素
    def element_click(self, selector=None, element=None, log_msg="element"):
        """
        可选择传入selector或者元素element，传入element时需传入log_msg作为打印日志的信息
        :param selector:
        :param element:
        :param log_msg: 传入element时需传入log_msg作为打印日志的信息
        :return:
        """
        if selector:
            el = self.find_element(selector)
        else:
            el = element
        try:
            el.click()
            logger.info("The element \" %s \" was clicked." % (selector if selector else log_msg))
        except Exception as e:
            logger.error("Failed to click the element \" %s \", reason: %s" % ((selector if selector else log_msg), e))
            self.get_windows_img("click_%s" % (selector if selector else log_msg))

    # 或者网页标题
    def get_page_title(self):
        logger.info("Current page title is %s" % self.driver.title)
        return self.driver.title

    # 切换frame
    def switch_frame(self, index=-1, frame_id="", frame_name="", selector="", parent_frame=0, default_content=0):
        """
         六种切换方式
        :param index: 用frame的index来定位，定位第一个frame （index 下标从0开始）
        :param frame_id: 用id定位
        :param frame_name: 用name定位
        :param selector: 用查找元素的方式定位
        :param parent_frame: 从子frame切回到父frame,默认为0--不切换，1--切换
        :param default_content: 切到frame中之后，我们便不能继续操作主文档的元素，这时如果想操作主文档内容，
        则需切回主文档, 默认为0--不切换，1--切换
        """
        try:
            if index > -1:
                self.driver.switch_to.frame(index)
            elif frame_id:
                self.driver.switch_to.frame(frame_id)
            elif frame_name:
                self.driver.switch_to.frame(frame_name)
            elif selector:
                self.driver.switch_to.frame(self.find_element(selector))
            elif parent_frame != 0:
                self.driver.switch_to.parent_frame()
            elif default_content != 0:
                self.driver.switch_to.default_content()
            else:
                logger.error("please choose a locate method!!")
        except Exception as e:
            logger.error("fail to switch frame! reason:%s" % e)
            self.get_windows_img(error_info="switch_frame")

    # 搜索页面元素中是否有相应文本
    def serch_text(self, text):
        """匹配成功re.search方法返回一个匹配的对象，否则返回None"""
        src = self.driver.page_source
        text_found = re.search(text, src)
        if text_found:
            logger.info("页面中搜索到了 %s 文本" % text)
        else:
            logger.error("页面中未搜索到 %s 文本" % text)
            self.get_windows_img("页面中未搜索到 %s 文本" % text)
        return text_found

    # 一直等待某个元素消失，默认超时10秒
    def is_not_visible(self, locator, timeout=10):
        """
        一直等待某个元素消失，默认超时10秒
        :param locator: 元素定位方式 格式：(By.CLASS_NAME, "layer-custom-text")
        :param timeout: 超时时间
        :return: 成功---True  失败--False
        """
        try:
            ui.WebDriverWait(self.driver, timeout).until_not(EC.visibility_of_element_located(locator))
            return True
        except TimeoutException:
            logger.warning(msg="fail to wait locator \"%s\" is not visible in %ss" % (locator, timeout))
            self.get_windows_img(error_info="wait not visible %s time out" % str(locator))
            return False

    # 一直等待某元素可见，元素的text不变，默认超时10秒
    # 此方法会一直等到元素且元素的text包含传入的text值时才会判定为成功
    def is_visible(self, locator, text=None, text_type="text", timeout=10):
        """
        一直等待某元素可见，元素的text值为传入的text，默认超时10秒
        :param locator:元素定位方式 格式：(By.CLASS_NAME, "layer-custom-text")
        :param text:等待的元素的文本，text为空则表示只等待元素是否出现，
        不为空则表示需判断出现的元素的text值包含传入的text值（注意：是包含关系，出现元素的text>=传入text即可）
        主要用于toast弹窗上的文字
        :param text_type:判断文本的方法，text--元素的text，value--元素的value属性
        :param timeout:超时时间
        :return:成功---True  失败--False
        """
        try:
            if text:
                if text_type == "text":
                    ui.WebDriverWait(self.driver, timeout).\
                        until(EC.text_to_be_present_in_element(locator=locator, text_=text))
                elif text_type == "value":
                    ui.WebDriverWait(self.driver, timeout).\
                        until(EC.text_to_be_present_in_element_value(locator=locator, text_=text))
                else:
                    raise Exception("text_type must be text or value")
            else:
                ui.WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator))
            logger.info(msg="success to wait locator \"%s\" is visible" % str(locator))
            return True
        except TimeoutException:
            # 没找到的情况
            logger.warning(msg="fail to wait locator \"%s\" is visible in %ss" % (str(locator), timeout))
            self.get_windows_img(error_info="fail to wait visible %s" % str(locator))
            return False

    # 一直等待某元素可见，且元素的text是在变化的，主要用于toast所以命名为toast_is_visible，默认超时10秒
    # 此方法会先判定元素是否出现，元素出现后再马上寻找此元素判定其text值是否包含传入的text，包含则成功，不包含则失败
    def toast_is_visible(self, locator, text=None, text_type="text", timeout=10):
        """
        一直等待某元素可见，元素的text值为传入的text，默认超时10秒
        :param locator:元素定位方式 格式：(By.CLASS_NAME, "layer-custom-text")
        :param text:等待的元素的文本，text为空则表示只等待元素是否出现，
        不为空则表示需判断出现的元素的text值包含传入的text值（注意：是包含关系，出现元素的text>=传入text即可）
        主要用于toast弹窗上的文字
        :param text_type:判断文本的方法，text--元素的text，value--元素的value属性
        :param timeout:超时时间
        :return:成功---True  失败--False
        """
        try:
            toast_el = ui.WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator))
        except TimeoutException:
            # 没找到的情况
            logger.warning(msg="fail to wait locator \"%s\" is visible in %ss" % (str(locator), timeout))
            self.get_windows_img(error_info="fail to wait visible %s" % str(locator))
            return False
        else:  # 等到元素显示出来
            if text:
                if text_type == "text":
                    if text in toast_el.text:
                        logger.info(msg="success to wait locator \"%s\" is visible" % str(locator))
                        return True
                    else:
                        logger.info(
                            msg="success to wait locator \"%s\" is visible, but text %s is not in the element"
                                % (str(locator), text))
                        self.get_windows_img(error_info="text %s is not in the element" % text)
                        return False
                elif text_type == "value":
                    if text in toast_el.get_attribute("value"):
                        logger.info(msg="success to wait locator \"%s\" is visible" % str(locator))
                        return True
                    else:
                        logger.info(
                            msg="success to wait locator \"%s\" is visible, but text %s is not in the element"
                                % (str(locator), text))
                        self.get_windows_img(error_info="text %s is not in the element" % text)
                        return False
                else:
                    raise Exception("text_type must be text or value")

    # 滚动到窗口中指定元素的位置
    def move_to_element(self, el=None, location="top", keys_down=0, keys_up=0, offset: int= 0):
        """
        滑动到窗口中指定元素的位置
        :param el: 元素
        :param location: top--滑动到窗口的最上面显示该元素， foot--窗口的最下面显示该元素
        :param keys_down  传入值则页面向下移动进行微调，通过模拟键盘DOWN（↓）来拖动，值越大拖动距离越大
        :param keys_up  传入值则页面向上移动进行微调，通过模拟键盘UP（↑）来拖动，值越大拖动距离越大
        :param offset  offset为负表示向下移动一个单位，为正表示向上移动一个单位
        :return:
        """
        # 滑动滚动条到某个指定的元素，如果元素为空，则直接按照偏移量滚动滚动条
        if el:
            if location == "top":
                js = "arguments[0].scrollIntoView();"  # 移动到元素element对象的“顶端”与当前窗口的“顶部”对齐  ：
            elif location == "foot":
                js = "arguments[0].scrollIntoView(false);"  # 移动到元素element对象的“底端”与当前窗口的“底部”对齐：
            else:
                raise Exception("location argument must be 'top' or 'foot', please check it!")
            # 将下拉滑动条滑动到当前div区域
            self.driver.execute_script(js, el)
            if keys_down != 0:
                for i in range(keys_down):
                    el.send_keys(Keys.DOWN)
            if keys_up != 0:
                for i in range(keys_up):
                    el.send_keys(Keys.UP)
        if offset != 0:
            # offset为负表示页面向下移动一个单位，为正表示向上移动一个单位
            win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, offset)  # 中间的0,0表示相对屏幕左上角坐标

    # 页面滚动到顶部或者底部
    def scroll_window(self, location="top"):
        if location == "top":
            self.driver.execute_script("window.scrollTo(document.body.scrollHeight,0)")
            logger.info("页面滚动到顶部")
        else:
            self.driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")
            logger.info("页面滚动到底部")

    # 鼠标悬停在元素上
    def mouse_stop_on_elment(self, element):
        try:
            ActionChains(self.driver).move_to_element(element).perform()  # 鼠标悬停
            logger.info("鼠标悬停！")
        except Exception as e:
            logger.error("鼠标悬停失败，reason: %s" % e)
            self.get_windows_img(error_info="鼠标悬停")  # take screenshot

    # 等待页面重新加载
    def wait_page_reload(self, element, timeout=10):
        # 根据页面刷新后元素的id会变化，等待元素过期则页面刷新成功
        logger.info("wait page reload!")
        try:
            ui.WebDriverWait(self, timeout).until(EC.staleness_of(element))
            logger.info("wait page reload success!")
            return True
        except TimeoutException:
            logger.info("wait page reload failed!")
            self.get_windows_img("wait page reload failed")
            return False
